Java 判断字节流是否是 UTF8 编码
Java 判断字节流是否是 UTF8 编码
遇到本来设计时使用 GBK 编码处理的地方,在实际使用过程导入了 UTF8 编码,造成了显示文本为乱码的现象,在了解 UTF8,GBK 编码和 Unicode 标准之后,编写了 Java 判断字节流是否是 UTF8 编码的程序,如果是 UTF8 编码,则转换成 GBK 编码。
编码的基础知识
Unicode 是一种标准,GBK 和 UTF8 是具体是编码格式。Java 的字符都是以 Unicode 进行存储的,占两或四个字节(看版本,且 Unicode 编码中对应关系是存在 0x00 的编码的)。Java 中的 getBytes() 方法是和平台(编码)相关的,在中文系统中返回的可能是 GBK 或 GBK2312,在英文系统中返回的可能是 ISO-8859-1。
- Unicode 标准:是计算机科学领域里的一项业界标准,包括字符集、编码方案等,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
- GBK 编码:汉字内码扩展规范,国标,汉字占两个字节。
- UTF8 编码:针对 Unicode 的可变长度字符编码,用 1 到 6 个字节编码 Unicode 字符,汉字一般占 3 个字节。
UTF8 编码格式
如果 Unicode 字符由 2 个字节表示,则编码成 UTF8 很可能需要 3 个字节。而如果 Unicode 字符由 4 个字节表示,则编码成 UTF8 可能需要 6个字节。用 4 个或 6 个字节去编码一个 Unicode 字符可能太多了,但很少会遇到那样的 Unicode 字符。
UTF8 编码规则:如果只有一个字节则其最高二进制位为 0,如果是多字节,其第一个字节从最高位开始,连续的二进制位值为 1,1 的个数决定了其编码的字节数,其余各字节均以 10 开头。
// Unicode6.1定义范围:0~10 FFFF
// 20 0000 ~ 3FF FFFF 和 400 0000 ~ 7FFF FFFF 属于 UCS-4,UTF8 现在已经弃用了这部分内容
---------------------------------------------------------------------------------
n | Unicode (十六进制) | UTF - 8 (二进制)
--+-----------------------+------------------------------------------------------
1 | 0000 0000 - 0000 007F | 0xxxxxxx
2 | 0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx
3 | 0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
4 | 0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
---------------------------------------------------------------------------------
// 以下部分弃用
5 | 0020 0000 - 03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6 | 0400 0000 - 7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
---------------------------------------------------------------------------------
Java 如何判断单个字符编码是否是 UTF8
假设当前需要判定一个 byte[] 数组内的编码是否是 UTF8 编码,这个 byte[] 是 String 通过 getBytes() 方法获取的,判断单个字符的编码步骤如下:
- 从 byte[] 数组中获取一个 byte 并将它转换成无符号类型的 int 变量 value
- 判断 value 是否是 ASCII 字符(小于 0x80)
- 判断 value 是否是无效字符(大于 0x80,小于 0xC0,参照 UTF8 编码规则)
- 确认该字符编码的是几字节 UTF8
- 确认该字符编码的除第一个字节外的字节是否满足 10xxxxxx 格式
PS:
Java getBytes() 获取的是带符号的十六进制,实际处理时需要使用无符号十六进制。
GBK 和 UTF8 中 ASCII 字符的值是一样的。
具体程序
将十六进制流中的所有编码按照单个判定的方式便利一遍,如果有不符合 UTF8 编码规则的字符出现,则该十六进制流就不是 UTF8 编码格式的字串。
public static int byteToUnsignedInt(byte data) {
return data & 0xff;
}
public boolean isUTF8(byte[] pBuffer) {
boolean IsUTF8 = true;
boolean IsASCII = true;
int size = pBuffer.length;
int i = 0;
while (i < size) {
int value = byteToUnsignedInt(pBuffer[i]);
if (value < 0x80) {
// (10000000): 值小于 0x80 的为 ASCII 字符
if (i >= size - 1) {
if (IsASCII) {
// 假设纯 ASCII 字符不是 UTF 格式
IsUTF8 = false;
}
break;
}
i++;
} else if (value < 0xC0) {
// (11000000): 值介于 0x80 与 0xC0 之间的为无效 UTF-8 字符
IsASCII = false;
IsUTF8 = false;
break;
} else if (value < 0xE0) {
// (11100000): 此范围内为 2 字节 UTF-8 字符
IsASCII = false;
if (i >= size - 1) {
break;
}
int value1 = byteToUnsignedInt(pBuffer[i + 1]);
if ((value1 & (0xC0)) != 0x80) {
IsUTF8 = false;
break;
}
i += 2;
} else if (value < 0xF0) {
IsASCII = false;
// (11110000): 此范围内为 3 字节 UTF-8 字符
if (i >= size - 2) {
break;
}
int value1 = byteToUnsignedInt(pBuffer[i + 1]);
int value2 = byteToUnsignedInt(pBuffer[i + 2]);
if ((value1 & (0xC0)) != 0x80 || (value2 & (0xC0)) != 0x80) {
IsUTF8 = false;
break;
}
i += 3;
} else if (value < 0xF8) {
IsASCII = false;
// (11111000): 此范围内为 4 字节 UTF-8 字符
if (i >= size - 3) {
break;
}
int value1 = byteToUnsignedInt(pBuffer[i + 1]);
int value2 = byteToUnsignedInt(pBuffer[i + 2]);
int value3 = byteToUnsignedInt(pBuffer[i + 3]);
if ((value1 & (0xC0)) != 0x80
|| (value2 & (0xC0)) != 0x80
|| (value3 & (0xC0)) != 0x80) {
IsUTF8 = false;
break;
}
i += 3;
} else {
IsUTF8 = false;
IsASCII = false;
break;
}
}
return IsUTF8;
}
UTF8 编码转 GBK 编码
// Unicode
String unicodeString = "张三";
// 获取 UTF8 编码
byte[] nameUTF8 = unicodeString.getBytes("utf-8");
// UTF8 编码转 str
String str = new String(name, "utf-8");
// 获取 GBK 编码
byte[] nameGBK = str.getBytes("gbk");